
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	DocView.m
 *
 *	This class handles the scaling of the drawing view. It repositions
 *	The drawing view within itself if the drawing view is smaller than the
 *	size of the clip view. The size of the doc view is:
 *	MAX(clip view frame, drawing view frame).
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "DocView.h"
#import "DrawingView.h"
#import "rotateprocs.h"

#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>

#import <appkit/Matrix.h>
#import <appkit/ScrollView.h>

#import <dpsclient/wraps.h>
#import <appkit/nextstd.h>

@implementation DocView

/*
*	Since there is only one subview its easier to keep track of it as an
*	instance variable rather than in the subview list.  Returns any the
*	previous drawing view.
*/
- addDrawView:subView
{
	id		oldView;

	oldView = drawView;
	[oldView  removeFromSuperview];

	[self  addSubview:subView];
	drawView = subView;
	
	return oldView;
}

- setZoomControl:anObject
{
	zoomControl = anObject;
	
	return self;
}

- zoomControl
{
	return zoomControl;
}

- setScale:(float)value
{
	scale = value;
	
	return self;
}

- (float) scale
{
	return  scale;
}

- (BOOL) isZooming
{
	return zooming;
}

/*
*	This method is overridden from the View class. When the
*	user is zooming the cursor rect for the zoom cursor is set
*	the the frame of the drawing view.
*/
- resetCursorRects
{
	id			imageId;

	NXPoint		hotspot;

	NXRect		cursorRect, visRect;

	if (zooming)
	{
		if (!zoomCursor)
		{
			hotspot.x = hotspot.y = 8.0;
			imageId = [NXImage  newFromSection:"zoom.tiff"];
			zoomCursor = [NXCursor  newFromImage:imageId];
			[zoomCursor setHotSpot:&hotspot];
		}

		[self getVisibleRect:&visRect];
		[drawView getFrame:&cursorRect];
		NXIntersectionRect(&visRect, &cursorRect);
		[self addCursorRect:&cursorRect  cursor:zoomCursor];
	}

	return self;
}

/*
*	Messaged from the zoom matrix. Starts the zoom loop. At the next
*	mouse down in the drawing view, the scaleDrawView:toPoint: method
*	will be called.
*/
- zoom:sender
{
	if (scale != ([sender  selectedTag] * 0.01))
	{
		zooming = YES;
		[self  setZoomControl:sender];
		[self  resetCursorRects];

		[window makeKeyAndOrderFront:self];
	}

	return self;
}

- rotateDrawView
{
	NXPoint		viewPt, windowPt;

	NXRect		tempRect;

	if (drawView)
	{
		[drawView  getVisibleRect:&tempRect];
		viewPt.x = tempRect.origin.x + tempRect.size.width/2.0;
		viewPt.y = tempRect.origin.y + tempRect.size.height/2.0;

		[self getVisibleRect:&tempRect];
		windowPt.x = tempRect.origin.x + tempRect.size.width/2.0;
		windowPt.y = tempRect.origin.y + tempRect.size.height/2.0;
		[self  convertPoint:&windowPt  toView:nil];

		[window disableDisplay];
			[self  placeDrawView];
			[self  scrollPoint:&viewPt  to:&windowPt];
		[window reenableDisplay];
		[[window  contentView]  display];
	}

	return self;
}

/*
*	Sizes the drawing view from the old scale to the newscale.
*	The frame of the view is used instead of the bounds because
*	the bound will always be the same whereas the frame will
*	provide the size in default coordinates.
*/
- sizeDrawViewWithScale:(float) newscale
{
	float			scalefactor;

	NXRect		viewFrame;
	
	[drawView  getFrame:&viewFrame];
	scalefactor = newscale/scale;
	[drawView sizeTo:rint(viewFrame.size.width*scalefactor)
				:rint(viewFrame.size.height*scalefactor)];

	return self;
}

/* 
*	Scales the drawing view from the old scale to the new scale.
*	The scale method is relative so we use the ratio
*	of the old scale to the new scale to obtain the scaling factor.
*/
- scaleDrawViewWithScale:(float) newscale
{
	float 	scalefactor;

	scalefactor = newscale/scale;
	[drawView scale:scalefactor  :scalefactor];

	return self;
}

/*
*	Place the drawing view in the center of the doc view
*	if it is smaller than the size of the ClipView.
*/
- placeDrawView
{
	int			margin;

	float			rads;
	
	NXSize		contSize, newSize, insetSize, delta;

	NXRect 		tempRect, viewFrame;

	if (drawView)
	{
		margin = (int) 4 * OFFSET * scale;

		rads = [drawView  rotation] / 180.0 * M_PI;
		[drawView  getFrame:&tempRect];
		RotateRectBounds(&viewFrame, &tempRect, &tempRect.origin, rads);
		[[superview  superview]  getContentSize:&contSize];

		newSize.width = MAX(viewFrame.size.width, contSize.width);
		newSize.height = MAX(viewFrame.size.height, contSize.height);

		delta.width = newSize.width - viewFrame.size.width;
		delta.height = newSize.height - viewFrame.size.height;
		if (delta.width != 0.0 || delta.height != 0.0)
		{
			if (delta.width < margin)
				newSize.width += margin - delta.width;
			if (delta.height < margin)
				newSize.height += margin - delta.height;
		}
	
		[self  sizeTo:newSize.width  :newSize.height];

		insetSize.width = floor((newSize.width - viewFrame.size.width)/2 +
				tempRect.origin.x - viewFrame.origin.x);
		insetSize.height =  floor((newSize.height - viewFrame.size.height)/2 +
				tempRect.origin.y - viewFrame.origin.y);
		[drawView  moveTo:insetSize.width  :insetSize.height];
		if (insetSize.width != 0 || insetSize.height != 0)
			[drawView  setClipping:YES];
		else
			[drawView setClipping:NO];
	}

	return self;
}

/*
*	Scales the drawing view to the tag of the selected matrix cell and
*	then scrolls to the point. The point is in window coordinates so
*	it must be converted to the drawing view  coordinates in order
*	to position it in the same relative place in the view.
*/
- scaleDrawViewToPoint:(NXPoint *) p;
{
	float			newscale;

	NXPoint		viewPt;

	[window invalidateCursorRectsForView:self];
	newscale = [zoomControl  selectedTag] * 0.01;
	if (drawView && newscale != scale)
	{
		viewPt = *p;
		[drawView  convertPoint:&viewPt fromView:nil];
		[window disableDisplay];
			/* Size and then scale the drawing view. The frame of the */
			/* drawing view must reflect the changed size. Scaling */
			/* alone will not change the frame. */
			[self  sizeDrawViewWithScale:newscale];
			[self  scaleDrawViewWithScale:newscale];
			[self  setScale:newscale];

			[self  placeDrawView];
			[self  scrollPoint:&viewPt  to:p];
		[window reenableDisplay];
		[[window contentView]  display];
	}
	zooming = NO;

	return self;
}

/*
 *	Use the point location relative to the window versus the point relative
 *	to the drawing view (before the scale) to determine where to scroll
 *	the drawing view. We want the same location in the drawing view to
 *	appear where the mouse downed occurred.
 */	
- scrollPoint:(const NXPoint *) aPt  to:(const NXPoint *) windowPt
{
	NXPoint		viewPt, scrollPt;

	NXRect 		clipFrame, visRect;

	[superview  getFrame:&clipFrame];
	if (frame.size.width > clipFrame.size.width || frame.size.height > clipFrame.size.height)
	{
		viewPt = *aPt;
		[drawView  convertPointToSuperview:&viewPt];

		scrollPt = *windowPt;
		[self  convertPoint:&scrollPt  fromView:nil];
		[self  getVisibleRect:&visRect];
		viewPt.x  -= (scrollPt.x - visRect.origin.x);
		viewPt.y  -= (scrollPt.y - visRect.origin.y);
		[self  scrollPoint:&viewPt];		
	}

	return self;
}

/*
 * This drawSelf method draws a border around the drawing view as well as a
 * drop shadow.  This is only done if it will be visible however. We tell if it is visible
 * if the rectangle passed in is not contained entirely within drawingView frame.
*/
- drawSelf:(NXRect *)r :(int) count
{
	int			rotation;

	float			rads;

	NXRect		drawRect, viewFrame;

	PSsetgray(NX_LTGRAY);
	NXRectFill(r);

	if (drawView)
	{
		rotation = [drawView  rotation];
		rads = rotation / 180.0 * M_PI;
		[drawView  getFrame:&drawRect];
		RotateRectBounds(&viewFrame, &drawRect, &drawRect.origin, rads);
		if (!NXContainsRect(&viewFrame, r) || IntersectsRotatedRect(r, &drawRect, rads))
		{
			PSgsave();
				PSrectclip(r->origin.x, r->origin.y, r->size.width, r->size.height);

				PStranslate(drawRect.origin.x, drawRect.origin.y);
				PSrotate(rotation);
				PSsetgray(NX_BLACK);
				PSsetlinewidth(LINEWIDTH * scale);
				PSrectfill(rint(OFFSET * scale),  rint(-OFFSET * scale),
					drawRect.size.width, drawRect.size.height);
				PSrectstroke(0.0, 0.0, drawRect.size.width, drawRect.size.height);
			PSgrestore();
		}
	}

	return self;			
}

@end

